package cas.ihep.evt.impl;

import cas.ihep.evt.EvtPDL;
import cas.ihep.pwa.evtgen.EvtRandom;
import org.luaj.vm2.LuaTable;
import org.luaj.vm2.LuaValue;

import java.sql.*;
import java.util.Arrays;
import java.util.List;

public class PgsqlEvtPDL implements EvtPDL{
    private Connection conn;

    @SuppressWarnings("StringBufferReplaceableByString")
    private String formatQuerySql(String field, String evtname){
        StringBuilder sb=new StringBuilder("select ");
        sb.append(field).append(" from particle,alias where particle.pname=\'");
        sb.append(evtname).append("\' or alias.pname=\'").append(evtname);
        sb.append("\' and alias.origin=particle.pname;");
        return sb.toString();
    }

    @SuppressWarnings("StringBufferReplaceableByString")
    private String formatUpdateSql(String field, String evtname, double val){
        StringBuilder sb=new StringBuilder("update particle,alias set ");
        sb.append(field).append('=').append(val).append(" where particle.pname=\'");
        sb.append(evtname).append("\' or alias.pname=\'").append(evtname);
        sb.append("\'  and alias.origin=particle.pname;");
        return sb.toString();
    }

    @SuppressWarnings("StringBufferReplaceableByString")
    private String formatUpdateSql(String field, String evtname, String val){
        StringBuilder sb=new StringBuilder("update particle,alias set ");
        sb.append(field).append('=').append(val).append(" where particle.pname=\'");
        sb.append(evtname).append("\' or alias.pname=\'").append(evtname);
        sb.append("\'  and alias.origin=particle.pname;");
        return sb.toString();
    }

    private double resolveDouble(String field, String evtname){
        try(Statement stmt=conn.createStatement()){
            String sql= formatQuerySql(field,evtname);
            try(ResultSet rs=stmt.executeQuery(sql)){
                if(rs.next()){
                    return rs.getDouble(field);
                }
                throw new RuntimeException("No such value `"+evtname+"` of field `"+field+'`');
            }
        }catch (SQLException e){
            throw new RuntimeException(e);
        }
    }

    @Override
    public double getMeanMass(String evt) {
        return resolveDouble("mass",evt);
    }

    @Override
    public double getMass(String evt) {
        try (Statement stmt=conn.createStatement()){
            String sql= formatQuerySql("mass,width,minmass,maxmass",evt);
            ResultSet rs=stmt.executeQuery(sql);
            double temp;
            if(rs.next()) {
                double width = rs.getDouble("width"), mass = rs.getDouble("mass");
                double massMin = rs.getDouble("minmass"), massMax = rs.getDouble("maxmass");
                double ymin, ymax;
                if (width < 0.0001) {
                    temp=mass;
                } else {
                    ymin = Math.atan(2.0 * (massMin - mass) / width);
                    ymax = Math.atan(2.0 * (massMax - mass) / width);
                    temp = (mass + ((width / 2.0) * Math.tan(EvtRandom.flat(ymin, ymax))));
                }
            }else{
                throw new RuntimeException("No such value: "+evt);
            }
            return temp;
        }catch (RuntimeException e){
            throw e;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    @Override
    public double getMaxMass(String evt) {
        return resolveDouble("maxmass",evt);
    }

    @Override
    public double getMinMass(String evt) {
        return resolveDouble("minmass",evt);
    }

    @Override
    public double getMaxRange(String evt) {
        return resolveDouble("maxrange",evt);
    }

    @Override
    public double getCtau(String evt) {
        return resolveDouble("ctau",evt);
    }

    private int resolveInt(String field, String evt){
        try(Statement stmt=conn.createStatement()){
            String sql= formatQuerySql(field,evt);
            try(ResultSet rs=stmt.executeQuery(sql)){
                if(rs.next()){
                    return rs.getInt(field);
                }
                throw new RuntimeException("No such value `"+evt+"` of field `"+field+'`');
            }
        }
        catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public int getStdHep(String evt) {
        return resolveInt("stdhep",evt);
    }

    @Override
    public int getLundKC(String evt) {
        return resolveInt("lundkc",evt);
    }

    @Override
    public int getChg3(String evt) {
        return resolveInt("chg3",evt);
    }

    @Override
    public double get2ndMass(String evt) {
        return resolveDouble("mass2nd",evt);
    }

    @Override
    public double get2ndWidth(String evt) {
        return resolveDouble("width2nd",evt);
    }

    @Override
    public double getWidth(String evt) {
        return resolveDouble("width",evt);
    }

    @Override
    public String getChgconj(String evt) {
        return null;
    }

    @Override
    public void reSetMass(String evt, double val) {
        reset("mass",evt,val);
    }

    @Override
    public void setLStype(String evt, String lstype) {
        try(Statement stmt=conn.createStatement()){
            String sql=formatUpdateSql("lstype",evt,lstype);
            stmt.executeUpdate(sql);
        }catch (SQLException e){
            throw new RuntimeException(e);
        }
    }

    private void reset(String field, String evt, double val){
        try(Statement stmt=conn.createStatement()){
            stmt.executeUpdate(formatUpdateSql(field,evt,val));
        }catch (SQLException e){
            throw new RuntimeException(e);
        }
    }

    @Override
    public void reSetWidth(String evt, double val) {
        reset("width",evt,val);
    }

    @Override
    public void reSetMaxMass(String evt, double val) {
        reset("maxmass",evt,val);
    }

    @Override
    public void reSetMinMass(String evt, double val) {
        reset("minmass",evt,val);
    }

    private void reset(String field,String evt,List<Double> val){
        try(Statement stmt=conn.createStatement()){
            StringBuilder sb=new StringBuilder("array[");
            int sz=val.size();
            for(int i=0;i<sz;i++){
                sb.append(val.get(i).doubleValue());
                if(i<sz-1){
                    sb.append(',');
                }
            }
            sb.append(']');
            String sql=formatUpdateSql(field,evt,sb.toString());
            stmt.executeUpdate(sql);
        }catch (SQLException e){
            throw new RuntimeException(e);
        }
    }

    @Override
    public void setVpars(String evt, List<Double> val) {
        reset("vpars",evt,val);
    }

    @Override
    public void setVres(String evt, List<Double> val) {
        reset("vres",evt,val);
    }

    @Override
    public void setVstrs(String evt, List<String> val) {
        try(Statement stmt=conn.createStatement()){
            StringBuilder sb=new StringBuilder("array[");
            int sz=val.size();
            for(int i=0;i<sz;i++){
                sb.append('\'').append(val.get(i)).append('\'');
                if(i<sz-1){
                    sb.append(',');
                }
            }
            sb.append(']');
            String sql=formatUpdateSql("vstrs",evt,sb.toString());
            stmt.executeUpdate(sql);
        }catch (SQLException e){
            throw new RuntimeException(e);
        }
    }

    @Override
    public String getLStype(String evt) {
        return null;
    }

    private List<Double> resolveDArray(String field,String evt){
        try(Statement stmt=conn.createStatement()){
            String sql= formatQuerySql(field,evt);
            try(ResultSet rs=stmt.executeQuery(sql)){
                if(rs.next()){
                    Array arr=rs.getArray(field);
                    Double[] darr=(Double[])arr.getArray();
                    return Arrays.asList(darr);
                }
                throw new RuntimeException("No such value `"+evt+"` of field `"+field+'`');
            }
        }catch (SQLException e){
            throw new RuntimeException(e);
        }
    }

    @Override
    public List<Double> getVpars(String evt) {
        return resolveDArray("vpars",evt);
    }

    @Override
    public List<Double> getVRes(String evt) {
        return resolveDArray("vres",evt);
    }

    @Override
    public List<String> getVstrs(String evt) {
        try(Statement stmt=conn.createStatement()){
            String sql= formatQuerySql("vstrs",evt);
            try(ResultSet rs=stmt.executeQuery(sql)){
                if(rs.next()){
                    Array arr=rs.getArray("vstrs");
                    return Arrays.asList((String[])arr.getArray());
                }
                throw new RuntimeException("No such value `"+evt+"` of field `vstrs`");
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    private double checkGetDouble(LuaValue val){
        if (val.isnumber()){
            return val.todouble();
        }
        throw new RuntimeException(val.toString()+" is not a double value");
    }

    private String checkGetString(LuaValue val){
        if (val.isstring()){
            return val.tojstring();
        }
        throw new RuntimeException(val.toString()+" is not a String value");
    }

    private int checkGetInt(LuaValue val){
        if(val.isnumber()){
            return val.toint();
        }
        throw new RuntimeException(val.toString()+" is not a numeric value");
    }

    public LuaValue pdtadd(LuaValue val){
        if(val.istable()){
            LuaTable table=(LuaTable)val;
            String pname=checkGetString(table.get(3));
            int stdhepid=checkGetInt(table.get(4));
            double mass=checkGetDouble(table.get(5)),pwidth=checkGetDouble(table.get(6)),pmaxwidth=checkGetDouble(table.get(7));
            int chg3=checkGetInt(table.get(8)),spin2=checkGetInt(table.get(9));
            double ctau=checkGetDouble(table.get(10));
            int lundkc=checkGetInt(table.get(11));
            StringBuilder sb=new StringBuilder("insert into particle values(");
            sb.append('\'').append(pname).append("\',");
            sb.append(mass).append(',').append(pwidth);
            return LuaValue.NIL;
        }
        return LuaValue.error("Invalid type of value");
    }
}
